package com.llongwill.slg.gateway.filter.exception.config;

import cn.dev33.satoken.exception.IdTokenInvalidException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.llongwill.slg.gateway.common.GwReqErrorCode;
import com.xiangff.base.framework.resp.ResponseParam;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.MethodNotSupportedException;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.el.MethodNotFoundException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 全局异常处理
 *
 * @author xiangff
 */
@Component
@Slf4j
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
    /**
     * 状态码Key
     */
    private static final String KEY_STATUS = "httpStatus";
    /**
     * 返回内容key
     */
    private static final String KEY_BODY = "body";
//    private Tracer tracer;
//
//    public void setTracer(Tracer tracer) {
//        this.tracer = tracer;
//    }

    private final ObjectMapper objectMapper = new ObjectMapper();
    /**
     * MessageReader
     */
    private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

    /**
     * MessageWriter
     */
    private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();

    /**
     * ViewResolvers
     */
    private List<ViewResolver> viewResolvers = Collections.emptyList();

    /**
     * 存储处理异常后的信息
     */
    private final ThreadLocal<Map<String, Object>> exceptionHandlerResult = new ThreadLocal<>();

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
        Assert.notNull(messageReaders, "'messageReaders' must not be null");
        this.messageReaders = messageReaders;
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    public void setViewResolvers(List<ViewResolver> viewResolvers) {
        this.viewResolvers = viewResolvers;
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
        Assert.notNull(messageWriters, "'messageWriters' must not be null");
        this.messageWriters = messageWriters;
    }

    /**
     * 全局异常处理器 根据需求自定义
     *
     * @param serverWebExchange exchange
     * @param throwable         throwable
     * @return void
     */
    @SneakyThrows
    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
        // 按照异常类型进行处理
        Map<String, Object> result = new HashMap<>(2, 1);

        //构建异常返回对象
        ResponseParam<HttpStatus> exceptionResponse = buildeExceptionResponse(serverWebExchange, throwable);
        HttpStatus httpStatus = exceptionResponse.getData();
        String body = objectMapper.writeValueAsString(exceptionResponse.setData(null));
        result.put(KEY_STATUS, httpStatus);
        result.put(KEY_BODY, body);

        if (serverWebExchange.getResponse().isCommitted()) {
            return Mono.error(throwable);
        }
        exceptionHandlerResult.set(result);
        ServerRequest newRequest = ServerRequest.create(serverWebExchange, this.messageReaders);
//        Tracer.Span span = tracer.currentSpan();
//        if (span != null) {
//            span.tag("exception", throwable.toString());
//            span.flush();
//        }
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
                .switchIfEmpty(Mono.error(throwable))
                .flatMap((handler) -> handler.handle(newRequest))
                .flatMap((response) -> write(serverWebExchange, response));

    }

    /**
     * 构建异常返回对象
     *
     * @param serverWebExchange 请求上下文
     * @param throwable         异常
     * @return 异常返回对象
     */
    public ResponseParam<HttpStatus> buildeExceptionResponse(ServerWebExchange serverWebExchange, Throwable throwable) {
        ServerHttpRequest request = serverWebExchange.getRequest();

        //非SaToken异常
        if (!(throwable instanceof SaTokenException)
                || throwable.getMessage().contains(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()))) {
            //请求方法不支持
            if (throwable instanceof MethodNotAllowedException || throwable instanceof MethodNotSupportedException || throwable instanceof MethodNotFoundException) {
                return ResponseParam.fail(HttpStatus.METHOD_NOT_ALLOWED, String.valueOf(HttpStatus.METHOD_NOT_ALLOWED.value()), HttpStatus.METHOD_NOT_ALLOWED.name());
            }

            //超时
            if(throwable instanceof ResponseStatusException){
                ResponseStatusException responseStatusException = (ResponseStatusException)throwable;
                return ResponseParam.fail(responseStatusException.getStatus(), String.valueOf(responseStatusException.getStatus().value()), responseStatusException.getStatus().name());
            }

            log.error("Exception occurred, url: {}", request.getURI().getPath());
            log.error("Exception occurred", throwable);
            return ResponseParam.fail(HttpStatus.INTERNAL_SERVER_ERROR, String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), HttpStatus.INTERNAL_SERVER_ERROR.name());
        }

        Throwable cause = throwable.getCause();
        // 异常处理 此处添加自定义异常处理
        return throwable.getCause() instanceof NotLoginException
                || throwable.getCause() instanceof IdTokenInvalidException
                || throwable instanceof NotLoginException
                || throwable instanceof IdTokenInvalidException
                ? ResponseParam.fail(HttpStatus.UNAUTHORIZED, String.valueOf(HttpStatus.UNAUTHORIZED.value()), GwReqErrorCode.UNLOGIN.getMessage())
                : ResponseParam.fail(HttpStatus.FORBIDDEN, String.valueOf(HttpStatus.FORBIDDEN.value()), GwReqErrorCode.NO_ROLE.getMessage());
    }

    /**
     * 参考DefaultErrorWebExceptionHandler
     */
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Map<String, Object> result = exceptionHandlerResult.get();
        exceptionHandlerResult.remove();
        return ServerResponse.status((HttpStatus) result.get(KEY_STATUS))
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromObject(result.get(KEY_BODY)));
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    private Mono<? extends Void> write(ServerWebExchange exchange,
                                       ServerResponse response) {
        exchange.getResponse().getHeaders()
                .setContentType(response.headers().getContentType());
        return response.writeTo(exchange, new ResponseContext());
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    private class ResponseContext implements ServerResponse.Context {


        @Override
        public List<HttpMessageWriter<?>> messageWriters() {
            return GlobalExceptionHandler.this.messageWriters;
        }

        @Override
        public List<ViewResolver> viewResolvers() {
            return GlobalExceptionHandler.this.viewResolvers;
        }

    }

}
